#include "local_system_time_impl.hh"

namespace kratos {
namespace time {
constexpr static WeekdayEnum weekdays[] = {
    WeekdayEnum::Sunday,    WeekdayEnum::Monday,   WeekdayEnum::Tuesday,
    WeekdayEnum::Wednesday, WeekdayEnum::Thursday, WeekdayEnum::Friday,
    WeekdayEnum::Saturday};
}
} // namespace kratos

kratos::time::LocalDateImpl::LocalDateImpl() {}

kratos::time::LocalDateImpl::~LocalDateImpl() {}

bool kratos::time::LocalDateImpl::from(std::time_t t) noexcept {
  try {
    datetime_ = make_unique_pool_ptr<time_system::Datetime>(t);
  } catch (std::exception &) {
    return false;
  }
  return true;
}

bool kratos::time::LocalDateImpl::from(const std::string &fmt) noexcept {
  try {
    datetime_ = make_unique_pool_ptr<time_system::Datetime>(fmt);
  } catch (std::exception &) {
    return false;
  }
  return true;
}

int kratos::time::LocalDateImpl::year() const {
  if (datetime_) {
    return datetime_->year;
  }
  return 0;
}

int kratos::time::LocalDateImpl::month() const {
  if (datetime_) {
    return datetime_->month;
  }
  return 0;
}

int kratos::time::LocalDateImpl::day() const {
  if (datetime_) {
    return datetime_->day;
  }
  return 0;
}

kratos::time::WeekdayEnum kratos::time::LocalDateImpl::weekday() const {
  if (datetime_) {
    auto index = static_cast<std::underlying_type<time_system::Weekday>::type>(
        datetime_->wday);
    return weekdays[index];
  }
  return WeekdayEnum::Sunday;
}

int kratos::time::LocalDateImpl::hour() const {
  if (datetime_) {
    return datetime_->hour;
  }
  return 0;
}

int kratos::time::LocalDateImpl::minute() const {
  if (datetime_) {
    return datetime_->minute;
  }
  return 0;
}

int kratos::time::LocalDateImpl::second() const {
  if (datetime_) {
    return datetime_->second;
  }
  return 0;
}

std::time_t kratos::time::LocalDateImpl::elapsed_seconds() const {
  if (datetime_) {
    return datetime_->get_second();
  }
  return 0;
}

int kratos::time::LocalDateImpl::elapsed_days() const {
  if (datetime_) {
    return kratos::time_system::TimeSystem::days_from_civil(
        datetime_->year, datetime_->month, datetime_->day);
  }
  return 0;
}

std::string kratos::time::LocalDateImpl::to_string() const {
  if (datetime_) {
    return datetime_->to_string();
  }
  return "";
}

kratos::time::WeekdayEnum kratos::time::LocalDateImpl::next_weekday() const {
  if (!datetime_) {
    return kratos::time::WeekdayEnum::None;
  }
  auto wday =
      std::underlying_type<kratos::time_system::Weekday>::type(datetime_->wday);
  return weekdays[wday < 6 ? wday + 1 : 0];
}

kratos::time::WeekdayEnum kratos::time::LocalDateImpl::prev_weekday() const {
  if (!datetime_) {
    return kratos::time::WeekdayEnum::None;
  }
  auto wday =
      std::underlying_type<kratos::time_system::Weekday>::type(datetime_->wday);
  return weekdays[wday > 0 ? wday - 1 : 6];
}

int kratos::time::LocalDateImpl::days_at_week() const {
  if (!datetime_) {
    return -1;
  }
  if (kratos::time_system::Weekday::Sunday == datetime_->wday) {
    return 7;
  }
  return (int)std::underlying_type<kratos::time_system::Weekday>::type(
      datetime_->wday);
}

int kratos::time::LocalDateImpl::days_at_month() const {
  if (!datetime_) {
    return -1;
  }
  return datetime_->day;
}

int kratos::time::LocalDateImpl::days_at_year() const {
  if (!datetime_) {
    return -1;
  }
  auto start_days = kratos::time_system::TimeSystem::days_from_civil<int>(
      datetime_->year, 1, 1);
  auto end_days = kratos::time_system::TimeSystem::days_from_civil<int>(
      datetime_->year, datetime_->month, datetime_->day);
  return (end_days - start_days) + 1;
}

int kratos::time::LocalDateImpl::days_of_month() const {
  if (!datetime_) {
    return -1;
  }
  return kratos::time_system::TimeSystem::last_day_of_month(datetime_->year,
                                                            datetime_->month);
}

int kratos::time::LocalDateImpl::days_of_year() const {
  if (!datetime_) {
    return -1;
  }
  auto start_days = kratos::time_system::TimeSystem::days_from_civil<int>(
      datetime_->year, 1, 1);
  auto end_days = kratos::time_system::TimeSystem::days_from_civil<int>(
      datetime_->year + 1, 1, 1);
  return (end_days - start_days);
}

bool kratos::time::LocalDateImpl::is_last_day_of_month() const {
  if (!datetime_) {
    return false;
  }
  return (kratos::time_system::TimeSystem::last_day_of_month(
              datetime_->year, datetime_->month) ==
          (unsigned int)(datetime_->day));
}

bool kratos::time::LocalDateImpl::is_last_day_of_year() const {
  if (!datetime_) {
    return false;
  }
  return (datetime_->month == 12 && datetime_->day == 31);
}

bool kratos::time::LocalDateImpl::is_last_day_of_week() const {
  if (!datetime_) {
    return false;
  }
  return (datetime_->wday == kratos::time_system::Weekday::Sunday);
}

kratos::time::LocalTimeImpl::LocalTimeImpl() {}

kratos::time::LocalTimeImpl::~LocalTimeImpl() {}

std::time_t kratos::time::LocalTimeImpl::get_millionsecond() noexcept {
  return time_system::TimeSystem::get_millionsecond();
}

std::time_t kratos::time::LocalTimeImpl::get_second() noexcept {
  return time_system::TimeSystem::get_second();
}

std::time_t kratos::time::LocalTimeImpl::utc_diff_second() noexcept {
  return time_system::TimeSystem::get_utc_diff_second();
}

int kratos::time::LocalTimeImpl::diff_days(std::time_t t) noexcept {
  return time_system::TimeSystem::CIVIL::get_days(t);
}

int kratos::time::LocalTimeImpl::diff_days(std::time_t t1,
                                           std::time_t t2) noexcept {
  return time_system::TimeSystem::CIVIL::get_days(t1, t2);
}

std::unique_ptr<kratos::time::LocalDate>
kratos::time::LocalTimeImpl::get_date() noexcept {
  auto *date = new kratos::time::LocalDateImpl();
  if (!date->from(get_second())) {
    return nullptr;
  }
  return std::unique_ptr<kratos::time::LocalDate>(date);
}

std::unique_ptr<kratos::time::LocalDate>
kratos::time::LocalTimeImpl::from(const std::string &fmt) noexcept {
  auto *date = new kratos::time::LocalDateImpl();
  if (!date->from(fmt)) {
    return nullptr;
  }
  return std::unique_ptr<kratos::time::LocalDate>(date);
}

std::unique_ptr<kratos::time::LocalDate>
kratos::time::LocalTimeImpl::from(std::time_t t) noexcept {
  auto *date = new kratos::time::LocalDateImpl();
  if (!date->from(t)) {
    return nullptr;
  }
  return std::unique_ptr<kratos::time::LocalDate>(date);
}

bool kratos::time::LocalTimeImpl::in_same_day(std::time_t t1,
                                              std::time_t t2) noexcept {
  return time_system::TimeSystem::CIVIL::in_same_day(t1, t2);
}

bool kratos::time::LocalTimeImpl::in_same_week(std::time_t t1,
                                               std::time_t t2) noexcept {
  return time_system::TimeSystem::CIVIL::in_same_week(t1, t2);
}

bool kratos::time::LocalTimeImpl::in_same_month(std::time_t t1,
                                                std::time_t t2) noexcept {
  return time_system::TimeSystem::CIVIL::in_same_month(t1, t2);
}
